package com.yugao.fintech.antelope.pay.payment.process.payorder;

import cn.hutool.core.date.DateUtil;
import com.github.yitter.idgen.YitIdHelper;
import com.yugao.fintech.antelope.base.security.utils.SecurityUtils;
import com.yugao.fintech.antelope.pay.common.constants.Cs;
import com.yugao.fintech.antelope.pay.common.constants.PayNodeEnum;
import com.yugao.fintech.antelope.pay.common.exception.ChannelException;
import com.yugao.fintech.antelope.pay.common.manager.PayMqSender;
import com.yugao.fintech.antelope.pay.common.manager.TradeDataManager;
import com.yugao.fintech.antelope.pay.common.model.mq.PayOrderCloseMQ;
import com.yugao.fintech.antelope.pay.common.service.MchPayPassageService;
import com.yugao.fintech.antelope.pay.common.service.PayNodeService;
import com.yugao.fintech.antelope.pay.common.service.PayOrderService;
import com.yugao.fintech.antelope.pay.common.service.PaySysConfigService;
import com.yugao.fintech.antelope.pay.common.utils.AmountUtils;
import com.yugao.fintech.antelope.pay.constant.IfCodeEnum;
import com.yugao.fintech.antelope.pay.constant.PayDataTypeEnum;
import com.yugao.fintech.antelope.pay.constant.PayWayCodeEnum;
import com.yugao.fintech.antelope.pay.model.bo.CashierTokenInfo;
import com.yugao.fintech.antelope.pay.payment.channel.AbstractPaymentService;
import com.yugao.fintech.antelope.pay.payment.channel.IPaymentService;
import com.yugao.fintech.antelope.pay.payment.model.DBApplicationConfig;
import com.yugao.fintech.antelope.pay.payment.model.bo.MchAppConfigContext;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.ChannelRetMsgExt;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.payorder.UnifiedOrderExtRQ;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.payorder.UnifiedOrderExtRS;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.payorder.payway.QrCashierOrderRQ;
import com.yugao.fintech.antelope.pay.payment.model.rqrs.payorder.payway.QrCashierOrderRS;
import com.yugao.fintech.antelope.pay.payment.process.ChannelPayOrderProcess;
import com.yugao.fintech.antelope.pay.payment.service.ConfigContextQueryService;
import com.yugao.fintech.framework.assistant.utils.StringUtils;
import com.yugao.fintech.framework.assistant.utils.exception.BizException;
import com.yugao.fintech.framework.core.response.Resp;
import com.yugao.fintech.framework.core.utils.IpUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.transaction.annotation.Transactional;

import java.math.BigDecimal;
import java.util.Date;
import java.util.Objects;
import java.util.Optional;

@Slf4j
public abstract class AbstractPayOrderProcess {
    @Autowired
    private MchPayPassageService mchPayPassageService;
    @Autowired
    private ConfigContextQueryService configContextQueryService;
    @Autowired
    private PayOrderService payOrderService;
    @Autowired
    private ChannelPayOrderProcess channelPayOrderProcess;
    @Autowired
    private PaySysConfigService paySysConfigService;
    @Autowired
    private PayMqSender payMqSender;
    @Autowired
    private PayNodeService payNodeService;
    @Autowired
    private TradeDataManager tradeDataManager;

    /**
     * 统一下单 (新建订单模式)
     */
    protected Resp<?> unifiedOrder(String wayCode, UnifiedOrderExtRQ bizRQ) {
        return unifiedOrder(wayCode, bizRQ, null);
    }

    /**
     * 统一下单
     */
    @Transactional(rollbackFor = Exception.class)
    protected Resp<?> unifiedOrder(String wayCode, UnifiedOrderExtRQ bizRQ, PayOrder payOrder) {
        // 响应数据
        UnifiedOrderExtRS bizRS = null;

        // 是否新订单模式 [  一般接口都为新订单模式，  由于QR_CASHIER支付方式，需要先 在DB插入一个新订单， 导致此处需要特殊判断下。 如果已存在则直接更新，否则为插入。  ]
        boolean isNewOrder = payOrder == null;
        PayNode payNode = PayNode.builder()
                .createdUid(SecurityUtils.getUserId())
                .titleCode(PayNodeEnum.PAY_INIT.getCode()).build();
        try {
            if (!isNewOrder) { //当订单存在时，封装公共参数。
                payNode.setPayOrderNo(payOrder.getPayOrderNo());

                // 如果支付状态为支付中-还能走到这里, 表示用户当前操作为继续支付
                if (!PayOrder.STATE_INIT.equals(payOrder.getStatus()) &&
                        !PayOrder.STATE_ING.equals(payOrder.getStatus()) ) {
                    throw new BizException("订单状态异常");
                }

                payOrder.setWayCode(wayCode); // 需要将订单更新 支付方式
                payOrder.setChannelUser(bizRQ.getChannelUserId()); //更新渠道用户信息
                bizRQ.setMchNo(payOrder.getMchNo());
                bizRQ.setAppId(payOrder.getAppId());
                bizRQ.setMchOrderNo(payOrder.getMchOrderNo());
                bizRQ.setWayCode(wayCode);
                bizRQ.setAmount(payOrder.getPayAmount());
                bizRQ.setCurrency(payOrder.getCurrency());
                bizRQ.setClientIp(payOrder.getClientIp());
                bizRQ.setGoodsSubject(payOrder.getGoodsSubject());
                bizRQ.setNotifyUrl(payOrder.getNotifyUrl());
                bizRQ.setReturnUrl(payOrder.getReturnUrl());
                bizRQ.setChannelExtra(payOrder.getChannelExtra());
                bizRQ.setExtParam(payOrder.getExtParam());
                bizRQ = bizRQ.buildBizRQ();
//                bizRQ.setDivisionMode(payOrder.getDivisionMode());
            }

            String mchNo = bizRQ.getMchNo();
            String appId = bizRQ.getAppId();

            // 只有新订单模式，进行校验
            if (isNewOrder && payOrderService.count(PayOrder.lqw()
                    .eq(PayOrder::getMchNo, mchNo).eq(PayOrder::getMchOrderNo, bizRQ.getMchOrderNo())) > 0) {
                throw new BizException("商户订单[" + bizRQ.getMchOrderNo() + "]已存在");
            }

            if (StringUtils.isNotEmpty(bizRQ.getNotifyUrl()) && !StringUtils.isAvailableUrl(bizRQ.getNotifyUrl())) {
                throw new BizException("异步通知地址协议仅支持http:// 或 https:// !");
            }
            if (StringUtils.isNotEmpty(bizRQ.getReturnUrl()) && !StringUtils.isAvailableUrl(bizRQ.getReturnUrl())) {
                throw new BizException("同步通知地址协议仅支持http:// 或 https:// !");
            }

            // 获取支付参数 (缓存数据) 和 商户信息
            MchAppConfigContext mchAppConfigContext = configContextQueryService.queryMchInfoAndAppInfo(mchNo, appId);
            if (mchAppConfigContext == null) {
                throw new BizException("获取商户应用信息失败");
            }

            MchInfo mchInfo = mchAppConfigContext.getMchInfo();
            MchApp mchApp = mchAppConfigContext.getMchApp();

            // 收银台支付并且只有新订单需要走这里，  收银台二次下单的wayCode应该为实际支付方式。
            if (isNewOrder && Cs.PAY_WAY_CODE.QR_CASHIER.equals(wayCode)) {
                // 生成订单
                payOrder = genPayOrder(bizRQ, mchInfo, mchApp, null, null);
                String payOrderNo = payOrder.getPayOrderNo();
                // 订单入库 订单状态： 生成状态  此时没有和任何上游渠道产生交互。
                payOrderService.save(payOrder);

                QrCashierOrderRS qrCashierOrderRS = new QrCashierOrderRS();
                QrCashierOrderRQ qrCashierOrderRQ = (QrCashierOrderRQ) bizRQ;

                DBApplicationConfig dbApplicationConfig = paySysConfigService.getDBApplicationConfig();

                CashierTokenInfo tokenInfo = CashierTokenInfo.builder()
                        .payOrderNo(payOrderNo).mchNo(mchNo).appId(appId)
                        .build();
                String payUrl = dbApplicationConfig.genUniJsapiPayUrl(tokenInfo);
                if (PayDataTypeEnum.CODE_IMG_URL.getCode().equals(qrCashierOrderRQ.getPayDataType())) { // 二维码地址
//                    qrCashierOrderRS.setCodeImgUrl(dbApplicationConfig.genScanImgUrl(payUrl));
                    qrCashierOrderRS.setCodeImgUrl(payUrl);
                } else { // 默认都为跳转地址方式
                    qrCashierOrderRS.setPayUrl(payUrl);
                }
                // 发送延时消息, 超时关闭订单
                payMqSender.send(PayOrderCloseMQ.build(payOrder.getPayOrderNo()));
                return packageApiResByPayOrder(bizRQ, qrCashierOrderRS, payOrder);
            }

            // 根据支付方式， 查询出 该商户 可用的支付接口
            MchPayPassage mchPayPassage = mchPayPassageService.getMchPayPassage(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), wayCode);
            if (mchPayPassage == null) {
                throw new BizException("商户应用不支持该支付方式");
            }

            //获取支付接口
            IPaymentService paymentService = checkMchWayCodeAndGetService(mchAppConfigContext, mchPayPassage);
            IfCodeEnum ifCode = paymentService.getIfCode();

            //生成订单
            if (isNewOrder) {
                payOrder = genPayOrder(bizRQ, mchInfo, mchApp, ifCode, mchPayPassage);
            } else {
                payOrder.setIfCode(ifCode.getCode());

                // 查询支付方式的费率，并 在更新ing时更新费率信息
                payOrder.setMchFeeRate(mchPayPassage.getRate());
                // 商户手续费,单位元
                payOrder.setMchFeeAmount(AmountUtils.calPercentageFee(payOrder.getPayAmount(), payOrder.getMchFeeRate()));
            }

            //预先校验
            String errMsg = paymentService.preCheck(bizRQ, payOrder);
            if (StringUtils.isNotEmpty(errMsg)) {
                throw new BizException(errMsg);
            }

            if (isNewOrder) {
                // 订单入库 订单状态： 生成状态  此时没有和任何上游渠道产生交互。
                payOrderService.save(payOrder);
            }

            // 保存交易内容
            TradeData tradeData = TradeData.builder()
                    .payOrderNo(payOrder.getPayOrderNo()).mchOrderNo(bizRQ.getMchOrderNo())
                    .mchNo(mchNo).appId(appId)
                    .tradeType(bizRQ.getTradeType()).tradeContent(bizRQ.getTradeContent())
                    .build();
            tradeDataManager.saveTradeData(tradeData);

            payNode.setPayOrderNo(payOrder.getPayOrderNo());

            // 调起上游支付接口
            bizRS = (UnifiedOrderExtRS) paymentService.pay(bizRQ, payOrder, mchAppConfigContext);
            // 发送延时消息, 超时关闭订单
            payMqSender.send(PayOrderCloseMQ.build(payOrder.getPayOrderNo()));

            // 处理上游返回数据
            this.processChannelMsg(payNode, bizRS.getChannelRetMsg(), payOrder);
            return packageApiResByPayOrder(bizRQ, bizRS, payOrder);
        } catch (BizException e) {
            payNode.setTitleCode(PayNodeEnum.PAY_FAIL.getCode());
            return Resp.fail(e.getMessage());
        } catch (ChannelException e) {
            payNode.setTitleCode(PayNodeEnum.PAY_FAIL.getCode());

            // 处理上游返回数据
            this.processChannelMsg(payNode, e.getChannelRetMsgExt(), payOrder);
            if (e.getChannelRetMsgExt().getChannelState() == ChannelRetMsgExt.ChannelState.SYS_ERROR) {
                return Resp.fail(e.getMessage());
            }

            return this.packageApiResByPayOrder(bizRQ, bizRS, payOrder);
        } catch (Exception e) {
            log.error("系统异常：", e);
            return Resp.fail("系统异常");
        } finally {
            payNodeService.savePayNode(payNode);
        }
    }

    private PayOrder genPayOrder(UnifiedOrderExtRQ rq, MchInfo mchInfo, MchApp mchApp, IfCodeEnum ifCode, MchPayPassage mchPayPassage) {

        PayOrder payOrder = new PayOrder();
        payOrder.setPayOrderNo(String.valueOf(YitIdHelper.nextId())); //生成订单ID
        payOrder.setMchNo(mchInfo.getMchNo()); //商户号
        payOrder.setIsvNo(mchInfo.getIsvNo()); //服务商号
        payOrder.setMchName(mchInfo.getMchShortName()); //商户名称（简称）
        payOrder.setMchType(mchInfo.getType()); //商户类型
        payOrder.setMchOrderNo(rq.getMchOrderNo()); //商户订单号
        payOrder.setAppId(mchApp.getId()); //商户应用appId
        payOrder.setIfCode(Optional.ofNullable(ifCode).map(IfCodeEnum::getCode).orElse("")); //接口代码
        payOrder.setWayCode(rq.getWayCode()); //支付方式
        payOrder.setPayAmount(rq.getAmount()); //订单金额

        if (mchPayPassage != null) {
            payOrder.setMchFeeRate(mchPayPassage.getRate()); //商户手续费费率快照
        } else {
            payOrder.setMchFeeRate(BigDecimal.ZERO); //预下单模式， 按照0计算入库， 后续进行更新
        }

        payOrder.setMchFeeAmount(AmountUtils.calPercentageFee(payOrder.getPayAmount(), payOrder.getMchFeeRate())); //商户手续费,单位分

        payOrder.setCurrency(rq.getCurrency()); //币种
        payOrder.setStatus(PayOrder.STATE_INIT); //订单状态, 默认订单生成状态
        payOrder.setClientIp(StringUtils.defaultIfEmpty(rq.getClientIp(), IpUtils.getHostIp())); //客户端IP
        payOrder.setGoodsSubject(rq.getGoodsSubject()); //商品标题
        payOrder.setGoodsBody(rq.getGoodsBody()); //商品描述信息
        payOrder.setChannelExtra(rq.getChannelExtra()); //特殊渠道发起的附件额外参数,  是否应该删除该字段了？？ 比如authCode不应该记录， 只是在传输阶段存在的吧？  之前的为了在payOrder对象需要传参。
        payOrder.setChannelUser(rq.getChannelUserId()); //渠道用户标志
        payOrder.setExtParam(rq.getExtParam()); //商户扩展参数
        payOrder.setNotifyUrl(rq.getNotifyUrl()); //异步通知地址
        payOrder.setReturnUrl(rq.getReturnUrl()); //页面跳转地址

        // 分账模式
//        payOrder.setDivisionMode(ObjectUtils.defaultIfNull(rq.getDivisionMode(), PayOrder.DIVISION_MODE_FORBID));

        Date nowDate = new Date();

        //订单过期时间 单位： 秒
        if (rq.getExpiredTime() != null) {
            payOrder.setExpiredTime(DateUtil.offsetSecond(nowDate, rq.getExpiredTime()));
        } else {
            payOrder.setExpiredTime(DateUtil.offsetHour(nowDate, 2)); //订单过期时间 默认两个小时
        }

        payOrder.setCreatedAt(nowDate); //订单创建时间
        return payOrder;
    }


    /**
     * 校验： 商户的支付方式是否可用
     * 返回： 支付接口
     */
    private IPaymentService checkMchWayCodeAndGetService(MchAppConfigContext mchAppConfigContext, MchPayPassage mchPayPassage) {

        // 接口代码
        IfCodeEnum ifCode = IfCodeEnum.ofByCode(mchPayPassage.getIfCode());
        IPaymentService paymentService = AbstractPaymentService.getService(ifCode, PayWayCodeEnum.ofByCode(mchPayPassage.getWayCode()));
        if (paymentService == null) {
            throw new BizException("无此支付通道接口");
        }

        if (!paymentService.isSupport(mchPayPassage.getWayCode())) {
            throw new BizException("接口不支持该支付方式");
        }

        if (MchInfo.TYPE_NORMAL.equals(mchAppConfigContext.getMchType())) { //普通商户
            if (configContextQueryService.queryNormalMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode) == null) {
                throw new BizException("商户应用参数未配置");
            }
        } else if (Objects.equals(mchAppConfigContext.getMchType(), MchInfo.TYPE_ISVSUB)) { //特约商户
            if (configContextQueryService.queryIsvsubMchParams(mchAppConfigContext.getMchNo(), mchAppConfigContext.getAppId(), ifCode) == null) {
                throw new BizException("特约商户参数未配置");
            }

            if (configContextQueryService.queryIsvParams(mchAppConfigContext.getMchInfo().getIsvNo(), ifCode) == null) {
                throw new BizException("服务商参数未配置");
            }
        }

        return paymentService;

    }


    /**
     * 处理返回的渠道信息，并更新订单状态
     * payOrder将对部分信息进行 赋值操作。
     */
    private void processChannelMsg(PayNode payNode, ChannelRetMsgExt channelRet,
                                   PayOrder payOrder) {


        // 对象为空 || 上游返回状态为空， 则无需操作
        if (channelRet == null || channelRet.getChannelState() == null || payOrder == null) {
            return;
        }

        // 明确成功
        if (ChannelRetMsgExt.ChannelState.CONFIRM_SUCCESS == channelRet.getChannelState()) {
            this.updateInitOrderStateThrowException(PayOrder.STATE_SUCCESS, payOrder, channelRet);
            // 订单支付成功，其他业务逻辑
            channelPayOrderProcess.confirmSuccess(payOrder);
            payNode.setTitleCode(PayNodeEnum.PAY_OK.getCode());
            // 明确失败
        } else if (ChannelRetMsgExt.ChannelState.CONFIRM_FAIL == channelRet.getChannelState()) {
            this.updateInitOrderStateThrowException(PayOrder.STATE_FAIL, payOrder, channelRet);
            payNode.setTitleCode(PayNodeEnum.PAY_FAIL.getCode());
            // 上游处理中 || 未知 || 上游接口返回异常  订单为支付中状态
        } else if (ChannelRetMsgExt.ChannelState.WAITING == channelRet.getChannelState() ||
                ChannelRetMsgExt.ChannelState.UNKNOWN == channelRet.getChannelState() ||
                ChannelRetMsgExt.ChannelState.API_RET_ERROR == channelRet.getChannelState()
        ) {
            this.updateInitOrderStateThrowException(PayOrder.STATE_ING, payOrder, channelRet);
            payNode.setTitleCode(PayNodeEnum.PAY_INIT.getCode());
            // 系统异常: 订单不再处理, 为: 生成状态
        } else if (ChannelRetMsgExt.ChannelState.SYS_ERROR == channelRet.getChannelState()) {
            payNode.setTitleCode(PayNodeEnum.PAY_FAIL.getCode());
        } else {
            payNode.setTitleCode(PayNodeEnum.PAY_FAIL.getCode());
            throw new BizException("ChannelState 返回异常！");
        }

        //判断是否需要轮询查单
        if (channelRet.isNeedQuery()) {
//            mqSender.send(PayOrderReissueMQ.build(payOrderId, 1), 5);
        }
    }


    /**
     * 更新订单状态 --> 订单生成--> 其他状态  (向外抛出异常)
     */
    private void updateInitOrderStateThrowException(String orderState, PayOrder payOrder, ChannelRetMsgExt channelRetMsgExt) {
        String srcStatus = payOrder.getStatus();
        payOrder.setStatus(orderState);
        payOrder.setChannelOrderNo(channelRetMsgExt.getChannelOrderId());
        payOrder.setErrCode(channelRetMsgExt.getChannelErrCode());
        payOrder.setErrMsg(channelRetMsgExt.getChannelErrMsg());

        // 聚合码场景 订单对象存在会员信息， 不可全部以上游为准。
        if (StringUtils.isNotEmpty(channelRetMsgExt.getChannelUserId())) {
            payOrder.setChannelUser(channelRetMsgExt.getChannelUserId());
        }

        boolean isSuccess;
        if (PayOrder.STATE_INIT.equals(srcStatus)) {
            isSuccess = payOrderService.updateInit2Ing(payOrder.getPayOrderNo(), payOrder);
            if (!isSuccess) {
                throw new BizException("更新订单异常!");
            }
        }

        isSuccess = payOrderService.updateIng2SuccessOrFail(payOrder.getPayOrderNo(), payOrder.getStatus(), channelRetMsgExt);
        if (!isSuccess) {
            throw new BizException("更新订单异常!");
        }
    }


    /**
     * 统一封装订单数据
     */
    private Resp<?> packageApiResByPayOrder(UnifiedOrderExtRQ bizRQ, UnifiedOrderExtRS bizRS, PayOrder payOrder) {
        if (Objects.isNull(bizRS)) {
            return Resp.fail();
        }
        // 返回接口数据
        bizRS.setPayOrderNo(payOrder.getPayOrderNo());
        bizRS.setOrderStatus(payOrder.getStatus());
        bizRS.setMchOrderNo(payOrder.getMchOrderNo());

        if (PayOrder.STATE_FAIL.equals(payOrder.getStatus())) {
            bizRS.setErrCode(bizRS.getChannelRetMsg() != null ? bizRS.getChannelRetMsg().getChannelErrCode() : null);
            bizRS.setErrMsg(bizRS.getChannelRetMsg() != null ? bizRS.getChannelRetMsg().getChannelErrMsg() : null);
        }

//        return ApiRes.okWithSign(bizRS, configContextQueryService.queryMchApp(bizRQ.getMchNo(), bizRQ.getAppId()).getAppSecret());
        return Resp.ok(bizRS);
    }
}
